/*
 * @[H]:  Copyright (c) 2021 Phytium Information Technology, Inc. 
 * 
 *  SPDX-License-Identifier: Apache-2.0. 
 * 
 * @Date: 2021-07-20 10:38:01
 * @LastEditTime: 2021-07-22 21:40:07
 * @Description:  Description of file
 * @Modify History: 
 * * * Ver   Who        Date         Changes
 * * ----- ------     --------    --------------------------------------
 */
#include "f_xmac_hw.h"
#include "f_xmac.h"
#include "f_xmac_bdring.h"
#include "f_xmac_bd.h"
#include "ft_types.h"
#include "f_xmac.h"
#include "string.h"

static void FXmacBdSetRxWrap(uintptr BdPtr);
static void FXMAC_BdSetTxWrap(uintptr BdPtr);

/************************** Variable Definitions *****************************/

/***************** Macros (Inline Functions) Definitions *********************/

/****************************************************************************
 * Move the BdPtr argument ahead an arbitrary number of BDs wrapping around
 * to the beginning of the ring if needed.
 *
 * We know if a wrapaound should occur if the new BdPtr is greater than
 * the high address in the ring OR if the new BdPtr crosses over the
 * 0xFFFFFFFF to 0 boundary. The latter test is a valid one since we do not
 * allow a BD space to span this boundary.
 *
 * @param ring_ptr is the ring BdPtr appears in
 * @param BdPtr on input is the starting BD position and on output is the
 *        final BD position
 * @param num_bd is the number of BD spaces to increment
 *
 ****************************************************************************/
#define FXMAC_RING_SEEKAHEAD(ring_ptr, BdPtr, num_bd)                               \
    {                                                                               \
        uintptr Addr = (uintptr)(void *)(BdPtr);                                    \
                                                                                    \
        Addr += ((ring_ptr)->separation * (num_bd));                                \
        if ((Addr > (ring_ptr)->high_bd_addr) || ((uintptr)(void *)(BdPtr) > Addr)) \
        {                                                                           \
            Addr -= (ring_ptr)->length;                                             \
        }                                                                           \
                                                                                    \
        (BdPtr) = (FXmacBd *)(void *)Addr;                                          \
    }

/****************************************************************************
 * Move the BdPtr argument backwards an arbitrary number of BDs wrapping
 * around to the end of the ring if needed.
 *
 * We know if a wrapaound should occur if the new BdPtr is less than
 * the base address in the ring OR if the new BdPtr crosses over the
 * 0xFFFFFFFF to 0 boundary. The latter test is a valid one since we do not
 * allow a BD space to span this boundary.
 *
 * @param ring_ptr is the ring BdPtr appears in
 * @param BdPtr on input is the starting BD position and on output is the
 *        final BD position
 * @param num_bd is the number of BD spaces to increment
 *
 ****************************************************************************/
#define FXMAC_RING_SEEKBACK(ring_ptr, BdPtr, num_bd)                                \
    {                                                                               \
        uintptr Addr = (uintptr)(void *)(BdPtr);                                    \
                                                                                    \
        Addr -= ((ring_ptr)->separation * (num_bd));                                \
        if ((Addr < (ring_ptr)->base_bd_addr) || ((uintptr)(void *)(BdPtr) < Addr)) \
        {                                                                           \
            Addr += (ring_ptr)->length;                                             \
        }                                                                           \
                                                                                    \
        (BdPtr) = (FXmacBd *)(void *)Addr;                                          \
    }

/*****************************************************************************/
/**
 * Using a memory segment allocated by the caller, create and setup the BD list
 * for the given DMA channel.
 *
 * @param ring_ptr is the instance to be worked on.
 * @param PhysAddr is the physical base address of user memory region.
 * @param VirtAddr is the virtual base address of the user memory region. If
 *        address translation is not being utilized, then VirtAddr should be
 *        equivalent to PhysAddr.
 * @param Alignment governs the byte alignment of individual BDs. This function
 *        will enforce a minimum alignment of 4 bytes with no maximum as long
 *        as it is specified as a power of 2.
 * @param bd_count is the number of BDs to setup in the user memory region. It
 *        is assumed the region is large enough to contain the BDs.
 *
 * @return
 *
 * - FT_SUCCESS if initialization was successful
 * - XST_NO_FEATURE if the provided instance is a non DMA type
 *   channel.
 * - FXMAC_ERR_INVALID_PARAM under any of the following conditions:
 *   1) PhysAddr and/or VirtAddr are not aligned to the given Alignment
 *      parameter.
 *   2) Alignment parameter does not meet minimum requirements or is not a
 *      power of 2 value.
 *   3) bd_count is 0.
 * - FXMAC_ERR_SG_LIST if the memory segment containing the list spans
 *   over address 0x00000000 in virtual address space.
 *
 * @note
 * Make sure to pass in the right alignment value.
 *****************************************************************************/

LONG FXmacBdRingCreate(FXmacBdRing *ring_ptr, uintptr phys_addr,
                       uintptr virt_addr, u32 alignment, u32 bd_count)
{
    u32 i;
    uintptr BdVirtAddr;
    uintptr BdPhyAddr;
    uintptr virt_addrLoc = virt_addr;

    /* In case there is a failure prior to creating list, make sure the
	 * following attributes are 0 to prevent calls to other functions
	 * from doing anything.
	 */
    ring_ptr->all_cnt = 0U;
    ring_ptr->free_cnt = 0U;
    ring_ptr->hw_cnt = 0U;
    ring_ptr->pre_cnt = 0U;
    ring_ptr->post_cnt = 0U;

    /* Make sure alignment parameter meets minimum requirements */
    if (alignment < (u32)FXMAC_DMABD_MINIMUM_ALIGNMENT)
    {
        return (LONG)(FXMAC_ERR_INVALID_PARAM);
    }

    /* Make sure alignment is a power of 2 */
    if (((alignment - 0x00000001U) & alignment) != 0x00000000U)
    {
        return (LONG)(FXMAC_ERR_INVALID_PARAM);
    }

    /* Make sure phys_addr and virt_addr are on same alignment */
    if (((phys_addr % alignment) != (u32)0) || ((virt_addrLoc % alignment) != (u32)0))
    {
        return (LONG)(FXMAC_ERR_INVALID_PARAM);
    }

    /* Is bd_count reasonable? */
    if (bd_count == 0x00000000U)
    {
        return (LONG)(FXMAC_ERR_INVALID_PARAM);
    }

    /* Figure out how many bytes will be between the start of adjacent BDs */
    ring_ptr->separation = ((u32)sizeof(FXmacBd));

    /* Must make sure the ring doesn't span address 0x00000000. If it does,
	 * then the next/prev BD traversal macros will fail.
	 */
    if (virt_addrLoc > ((virt_addrLoc + (ring_ptr->separation * bd_count)) - (u32)1))
    {
        return (LONG)(FXMAC_ERR_SG_LIST);
    }

    /* Initial ring setup:
	 *  - Clear the entire space
	 *  - Setup each BD's BDA field with the physical address of the next BD
	 */
    (void)memset((void *)virt_addrLoc, 0, (ring_ptr->separation * bd_count));

    BdVirtAddr = virt_addrLoc;
    BdPhyAddr = phys_addr + ring_ptr->separation;
    for (i = 1U; i < bd_count; i++)
    {
        BdVirtAddr += ring_ptr->separation;
        BdPhyAddr += ring_ptr->separation;
    }

    /* Setup and initialize pointers and counters */
    ring_ptr->run_state = (u32)(FXMAC_DMA_SG_IS_STOPED);
    ring_ptr->base_bd_addr = virt_addrLoc;
    ring_ptr->phys_base_addr = phys_addr;
    ring_ptr->high_bd_addr = BdVirtAddr;
    ring_ptr->length =
        ((ring_ptr->high_bd_addr - ring_ptr->base_bd_addr) + ring_ptr->separation);
    ring_ptr->all_cnt = (u32)bd_count;
    ring_ptr->free_cnt = (u32)bd_count;
    ring_ptr->free_head = (FXmacBd *)(void *)virt_addrLoc;
    ring_ptr->pre_head = (FXmacBd *)virt_addrLoc;
    ring_ptr->hw_head = (FXmacBd *)virt_addrLoc;
    ring_ptr->hw_tail = (FXmacBd *)virt_addrLoc;
    ring_ptr->post_head = (FXmacBd *)virt_addrLoc;
    ring_ptr->bda_restart = (FXmacBd *)(void *)phys_addr;

    return (LONG)(FT_SUCCESS);
}

LONG FXmacBdRingClone(FXmacBdRing *ring_ptr, FXmacBd *src_bd_ptr,
                      u8 direction)
{
    u32 i;
    uintptr CurBd;

    /* Can't do this function if there isn't a ring */
    if (ring_ptr->all_cnt == 0x00000000U)
    {
        return (LONG)(FXMAC_ERR_SG_NO_LIST);
    }

    /* Can't do this function with the channel running */
    if (ring_ptr->run_state == (u32)FXMAC_DMA_SG_IS_STARTED)
    {
        return (LONG)(FXMAC_STATUS_IS_STARTED);
    }

    /* Can't do this function with some of the BDs in use */
    if (ring_ptr->free_cnt != ring_ptr->all_cnt)
    {
        return (LONG)(FXMAC_ERR_SG_LIST);
    }

    if ((direction != (u8)FXMAC_SEND) && (direction != (u8)FXMAC_RECV))
    {
        return (LONG)(FXMAC_ERR_INVALID_PARAM);
    }

    /* Starting from the top of the ring, save BD.Next, overwrite the entire
	 * BD with the template, then restore BD.Next
	 */
    CurBd = ring_ptr->base_bd_addr;
    for (i = 0U; i < ring_ptr->all_cnt; i++)
    {
        memcpy((void *)CurBd, src_bd_ptr, sizeof(FXmacBd));
        CurBd += ring_ptr->separation;
    }

    CurBd -= ring_ptr->separation;

    if (direction == FXMAC_RECV)
    {
        FXmacBdSetRxWrap(CurBd);
    }
    else
    {
        FXMAC_BdSetTxWrap(CurBd);
    }

    return (LONG)(FT_SUCCESS);
}

/*****************************************************************************/
/**
 * Reserve locations in the BD list. The set of returned BDs may be modified
 * in preparation for future DMA transaction(s). Once the BDs are ready to be
 * submitted to hardware, the user must call FXmacBdRingToHw() in the same
 * order which they were allocated here. Example:
 *
 * <pre>
 *        num_bd = 2,
 *        status = FXmacBdRingAlloc(Myring_ptr, num_bd, &MyBdSet),
 *
 *        if (status != FT_SUCCESS)
 *        {
 *            *Not enough BDs available for the request*
 *        }
 *
 *        CurBd = MyBdSet,
 *        for (i=0; i<num_bd; i++)
 *        {
 *            * Prepare CurBd *.....
 *
 *            * Onto next BD *
 *            CurBd = FXmacBdRingNext(Myring_ptr, CurBd),
 *        }
 *
 *        * Give list to hardware *
 *        status = FXmacBdRingToHw(Myring_ptr, num_bd, MyBdSet),
 * </pre>
 *
 * A more advanced use of this function may allocate multiple sets of BDs.
 * They must be allocated and given to hardware in the correct sequence:
 * <pre>
 *        * Legal *
 *        FXmacBdRingAlloc(Myring_ptr, num_bd1, &MySet1),
 *        FXmacBdRingToHw(Myring_ptr, num_bd1, MySet1),
 *
 *        * Legal *
 *        FXmacBdRingAlloc(Myring_ptr, num_bd1, &MySet1),
 *        FXmacBdRingAlloc(Myring_ptr, num_bd2, &MySet2),
 *        FXmacBdRingToHw(Myring_ptr, num_bd1, MySet1),
 *        FXmacBdRingToHw(Myring_ptr, num_bd2, MySet2),
 *
 *        * Not legal *
 *        FXmacBdRingAlloc(Myring_ptr, num_bd1, &MySet1),
 *        FXmacBdRingAlloc(Myring_ptr, num_bd2, &MySet2),
 *        FXmacBdRingToHw(Myring_ptr, num_bd2, MySet2),
 *        FXmacBdRingToHw(Myring_ptr, num_bd1, MySet1),
 * </pre>
 *
 * Use the API defined in FXmacbd.h to modify individual BDs. Traversal
 * of the BD set can be done using FXmacBdRingNext() and
 * FXmacBdRingPrev().
 *
 * @param ring_ptr is a pointer to the BD ring instance to be worked on.
 * @param num_bd is the number of BDs to allocate
 * @param bd_set_ptr is an output parameter, it points to the first BD available
 *        for modification.
 *
 * @return
 *   - FT_SUCCESS if the requested number of BDs was returned in the bd_set_ptr
 *     parameter.
 *   - FXMAC_FAILUE if there were not enough free BDs to satisfy the request.
 *
 * @note This function should not be preempted by another FXmacBd function
 *       call that modifies the BD space. It is the caller's responsibility to
 *       provide a mutual exclusion mechanism.
 *
 * @note Do not modify more BDs than the number requested with the num_bd
 *       parameter. Doing so will lead to data corruption and system
 *       instability.
 *
 *****************************************************************************/
LONG FXmacBdRingAlloc(FXmacBdRing *ring_ptr, u32 num_bd,
                      FXmacBd **bd_set_ptr)
{
    LONG status;
    /* Enough free BDs available for the request? */
    if (ring_ptr->free_cnt < num_bd)
    {
        status = (LONG)(FXMAC_FAILUE);
    }
    else
    {
        /* Set the return argument and move free_head forward */
        *bd_set_ptr = ring_ptr->free_head;
        FXMAC_RING_SEEKAHEAD(ring_ptr, ring_ptr->free_head, num_bd);
        ring_ptr->free_cnt -= num_bd;
        ring_ptr->pre_cnt += num_bd;
        status = (LONG)(FT_SUCCESS);
    }
    return status;
}

/*****************************************************************************/
/**
 * Fully or partially undo an FXmacBdRingAlloc() operation. Use this
 * function if all the BDs allocated by FXmacBdRingAlloc() could not be
 * transferred to hardware with FXmacBdRingToHw().
 *
 * This function helps out in situations when an unrelated error occurs after
 * BDs have been allocated but before they have been given to hardware.
 * An example of this type of error would be an OS running out of resources.
 *
 * This function is not the same as FXmacBdRingFree(). The Free function
 * returns BDs to the free list after they have been processed by hardware,
 * while UnAlloc returns them before being processed by hardware.
 *
 * There are two scenarios where this function can be used. Full UnAlloc or
 * Partial UnAlloc. A Full UnAlloc means all the BDs Alloc'd will be returned:
 *
 * <pre>
 *    status = FXmacBdRingAlloc(Myring_ptr, 10, &BdPtr),
 *        ...
 *    if (Error)
 *    {
 *        status = FXmacBdRingUnAlloc(Myring_ptr, 10, &BdPtr),
 *    }
 * </pre>
 *
 * A partial UnAlloc means some of the BDs Alloc'd will be returned:
 *
 * <pre>
 *    status = FXmacBdRingAlloc(Myring_ptr, 10, &BdPtr),
 *    BdsLeft = 10,
 *    cur_bd_ptr = BdPtr,
 *
 *    while (BdsLeft)
 *    {
 *       if (Error)
 *       {
 *          status = FXmacBdRingUnAlloc(Myring_ptr, BdsLeft, cur_bd_ptr),
 *       }
 *
 *       cur_bd_ptr = FXmacBdRingNext(Myring_ptr, cur_bd_ptr),
 *       BdsLeft--,
 *    }
 * </pre>
 *
 * A partial UnAlloc must include the last BD in the list that was Alloc'd.
 *
 * @param ring_ptr is a pointer to the instance to be worked on.
 * @param num_bd is the number of BDs to allocate
 * @param bd_set_ptr is an output parameter, it points to the first BD available
 *        for modification.
 *
 * @return
 *   - FT_SUCCESS if the BDs were unallocated.
 *   - FXMAC_FAILUE if num_bd parameter was greater that the number of BDs in
 *     the preprocessing state.
 *
 * @note This function should not be preempted by another FXmacBd function
 *       call that modifies the BD space. It is the caller's responsibility to
 *       provide a mutual exclusion mechanism.
 *
 *****************************************************************************/
LONG FXmacBdRingUnAlloc(FXmacBdRing *ring_ptr, u32 num_bd,
                        FXmacBd *bd_set_ptr)
{
    LONG status;
    (void)bd_set_ptr;
    FT_ASSERTNONVOID(ring_ptr != NULL);
    FT_ASSERTNONVOID(bd_set_ptr != NULL);

    /* Enough BDs in the free state for the request? */
    if (ring_ptr->pre_cnt < num_bd)
    {
        status = (LONG)(FXMAC_FAILUE);
    }
    else
    {
        /* Set the return argument and move free_head backward */
        FXMAC_RING_SEEKBACK(ring_ptr, (ring_ptr->free_head), num_bd);
        ring_ptr->free_cnt += num_bd;
        ring_ptr->pre_cnt -= num_bd;
        status = (LONG)(FT_SUCCESS);
    }
    return status;
}

/*****************************************************************************/
/**
 * Enqueue a set of BDs to hardware that were previously allocated by
 * FXmacBdRingAlloc(). Once this function returns, the argument BD set goes
 * under hardware control. Any changes made to these BDs after this point will
 * corrupt the BD list leading to data corruption and system instability.
 *
 * The set will be rejected if the last BD of the set does not mark the end of
 * a packet (see FXmacBdSetLast()).
 *
 * @param ring_ptr is a pointer to the instance to be worked on.
 * @param num_bd is the number of BDs in the set.
 * @param bd_set_ptr is the first BD of the set to commit to hardware.
 *
 * @return
 *   - FT_SUCCESS if the set of BDs was accepted and enqueued to hardware.
 *   - XST_FAILURE if the set of BDs was rejected because the last BD of the set
 *     did not have its "last" bit set.
 *   - FXMAC_ERR_SG_LIST if this function was called out of sequence with
 *     FXmacBdRingAlloc().
 *
 * @note This function should not be preempted by another FXmacBd function
 *       call that modifies the BD space. It is the caller's responsibility to
 *       provide a mutual exclusion mechanism.
 *
 *****************************************************************************/
LONG FXmacBdRingToHw(FXmacBdRing *ring_ptr, u32 num_bd,
                     FXmacBd *bd_set_ptr)
{
    FXmacBd *cur_bd_ptr;
    u32 i;
    LONG status;
    /* if no bds to process, simply return. */
    if (0U == num_bd)
    {
        status = (LONG)(FT_SUCCESS);
    }
    else
    {
        /* Make sure we are in sync with FXmacBdRingAlloc() */
        if ((ring_ptr->pre_cnt < num_bd) || (ring_ptr->pre_head != bd_set_ptr))
        {
            status = (LONG)(FXMAC_ERR_SG_LIST);
        }
        else
        {
            cur_bd_ptr = bd_set_ptr;
            for (i = 0U; i < num_bd; i++)
            {
                cur_bd_ptr = (FXmacBd *)((void *)FXmacBdRingNext(ring_ptr, cur_bd_ptr));
            }
            /* Adjust ring pointers & counters */
            FXMAC_RING_SEEKAHEAD(ring_ptr, ring_ptr->pre_head, num_bd);
            ring_ptr->pre_cnt -= num_bd;
            ring_ptr->hw_tail = cur_bd_ptr;
            ring_ptr->hw_cnt += num_bd;

            status = (LONG)(FT_SUCCESS);
        }
    }
    return status;
}

/*****************************************************************************/
/**
 * Returns a set of BD(s) that have been processed by hardware. The returned
 * BDs may be examined to determine the outcome of the DMA transaction(s).
 * Once the BDs have been examined, the user must call FXmacBdRingFree()
 * in the same order which they were retrieved here. Example:
 *
 * <pre>
 *        num_bd = FXmacBdRingFromHwTx(Myring_ptr, MaxBd, &MyBdSet),
 *        if (num_bd == 0)
 *        {
 *           * hardware has nothing ready for us yet*
 *        }
 *
 *        CurBd = MyBdSet,
 *        for (i=0; i<num_bd; i++)
 *        {
 *           * Examine CurBd for post processing *.....
 *
 *           * Onto next BD *
 *           CurBd = FXmacBdRingNext(Myring_ptr, CurBd),
 *           }
 *
 *           FXmacBdRingFree(Myring_ptr, num_bd, MyBdSet),  *Return list*
 *        }
 * </pre>
 *
 * A more advanced use of this function may allocate multiple sets of BDs.
 * They must be retrieved from hardware and freed in the correct sequence:
 * <pre>
 *        * Legal *
 *        FXmacBdRingFromHwTx(Myring_ptr, num_bd1, &MySet1),
 *        FXmacBdRingFree(Myring_ptr, num_bd1, MySet1),
 *
 *        * Legal *
 *        FXmacBdRingFromHwTx(Myring_ptr, num_bd1, &MySet1),
 *        FXmacBdRingFromHwTx(Myring_ptr, num_bd2, &MySet2),
 *        FXmacBdRingFree(Myring_ptr, num_bd1, MySet1),
 *        FXmacBdRingFree(Myring_ptr, num_bd2, MySet2),
 *
 *        * Not legal *
 *        FXmacBdRingFromHwTx(Myring_ptr, num_bd1, &MySet1),
 *        FXmacBdRingFromHwTx(Myring_ptr, num_bd2, &MySet2),
 *        FXmacBdRingFree(Myring_ptr, num_bd2, MySet2),
 *        FXmacBdRingFree(Myring_ptr, num_bd1, MySet1),
 * </pre>
 *
 * If hardware has only partially completed a packet spanning multiple BDs,
 * then none of the BDs for that packet will be included in the results.
 *
 * @param ring_ptr is a pointer to the instance to be worked on.
 * @param bd_limit is the maximum number of BDs to return in the set.
 * @param bd_set_ptr is an output parameter, it points to the first BD available
 *        for examination.
 *
 * @return
 *   The number of BDs processed by hardware. A value of 0 indicates that no
 *   data is available. No more than bd_limit BDs will be returned.
 *
 * @note Treat BDs returned by this function as read-only.
 *
 * @note This function should not be preempted by another FXmacBd function
 *       call that modifies the BD space. It is the caller's responsibility to
 *       provide a mutual exclusion mechanism.
 *
 *****************************************************************************/
u32 FXmacBdRingFromHwTx(FXmacBdRing *ring_ptr, u32 bd_limit,
                        FXmacBd **bd_set_ptr)
{
    FXmacBd *cur_bd_ptr;
    u32 bd_str = 0U;
    u32 bd_count;
    u32 bd_partial_count;
    u32 Sop = 0U;
    u32 status;
    u32 bd_limitLoc = bd_limit;
    cur_bd_ptr = ring_ptr->hw_head;
    bd_count = 0U;
    bd_partial_count = 0U;

    /* If no BDs in work group, then there's nothing to search */
    if (ring_ptr->hw_cnt == 0x00000000U)
    {
        *bd_set_ptr = NULL;
        status = 0U;
    }
    else
    {

        if (bd_limitLoc > ring_ptr->hw_cnt)
        {
            bd_limitLoc = ring_ptr->hw_cnt;
        }
        /* Starting at hw_head, keep moving forward in the list until:
	 *  - A BD is encountered with its new/used bit set which means
	 *    hardware has not completed processing of that BD.
	 *  - ring_ptr->hw_tail is reached and ring_ptr->hw_cnt is reached.
	 *  - The number of requested BDs has been processed
	 */
        while (bd_count < bd_limitLoc)
        {
            /* Read the status */
            if (cur_bd_ptr != NULL)
            {
                bd_str = FXmacBdRead(cur_bd_ptr, FXMAC_BD_STAT_OFFSET);
            }

            if ((Sop == 0x00000000U) && ((bd_str & FXMAC_TXBUF_USED_MASK) != 0x00000000U))
            {
                Sop = 1U;
            }
            if (Sop == 0x00000001U)
            {
                bd_count++;
                bd_partial_count++;
            }

            /* hardware has processed this BD so check the "last" bit.
		 * If it is clear, then there are more BDs for the current
		 * packet. Keep a count of these partial packet BDs.
		 */
            if ((Sop == 0x00000001U) && ((bd_str & FXMAC_TXBUF_LAST_MASK) != 0x00000000U))
            {
                Sop = 0U;
                bd_partial_count = 0U;
            }

            /* Move on to next BD in work group */
            cur_bd_ptr = FXmacBdRingNext(ring_ptr, cur_bd_ptr);
        }

        /* Subtract off any partial packet BDs found */
        bd_count -= bd_partial_count;

        /* If bd_count is non-zero then BDs were found to return. Set return
	 * parameters, update pointers and counters, return success
	 */
        if (bd_count > 0x00000000U)
        {
            *bd_set_ptr = ring_ptr->hw_head;
            ring_ptr->hw_cnt -= bd_count;
            ring_ptr->post_cnt += bd_count;
            FXMAC_RING_SEEKAHEAD(ring_ptr, ring_ptr->hw_head, bd_count);
            status = (bd_count);
        }
        else
        {
            *bd_set_ptr = NULL;
            status = 0U;
        }
    }
    return status;
}

/*****************************************************************************/
/**
 * Returns a set of BD(s) that have been processed by hardware. The returned
 * BDs may be examined to determine the outcome of the DMA transaction(s).
 * Once the BDs have been examined, the user must call FXmacBdRingFree()
 * in the same order which they were retrieved here. Example:
 *
 * <pre>
 *        num_bd = FXmacBdRingFromHwRx(Myring_ptr, MaxBd, &MyBdSet),
 *
 *        if (num_bd == 0)
 *        {
 *           *hardware has nothing ready for us yet*
 *        }
 *
 *        CurBd = MyBdSet,
 *        for (i=0; i<num_bd; i++)
 *        {
 *           * Examine CurBd for post processing *.....
 *
 *           * Onto next BD *
 *           CurBd = FXmacBdRingNext(Myring_ptr, CurBd),
 *           }
 *
 *           FXmacBdRingFree(Myring_ptr, num_bd, MyBdSet),  * Return list *
 *        }
 * </pre>
 *
 * A more advanced use of this function may allocate multiple sets of BDs.
 * They must be retrieved from hardware and freed in the correct sequence:
 * <pre>
 *        * Legal *
 *        FXmacBdRingFromHwRx(Myring_ptr, num_bd1, &MySet1),
 *        FXmacBdRingFree(Myring_ptr, num_bd1, MySet1),
 *
 *        * Legal *
 *        FXmacBdRingFromHwRx(Myring_ptr, num_bd1, &MySet1),
 *        FXmacBdRingFromHwRx(Myring_ptr, num_bd2, &MySet2),
 *        FXmacBdRingFree(Myring_ptr, num_bd1, MySet1),
 *        FXmacBdRingFree(Myring_ptr, num_bd2, MySet2),
 *
 *        * Not legal *
 *        FXmacBdRingFromHwRx(Myring_ptr, num_bd1, &MySet1),
 *        FXmacBdRingFromHwRx(Myring_ptr, num_bd2, &MySet2),
 *        FXmacBdRingFree(Myring_ptr, num_bd2, MySet2),
 *        FXmacBdRingFree(Myring_ptr, num_bd1, MySet1),
 * </pre>
 *
 * If hardware has only partially completed a packet spanning multiple BDs,
 * then none of the BDs for that packet will be included in the results.
 *
 * @param ring_ptr is a pointer to the instance to be worked on.
 * @param bd_limit is the maximum number of BDs to return in the set.
 * @param bd_set_ptr is an output parameter, it points to the first BD available
 *        for examination.
 *
 * @return
 *   The number of BDs processed by hardware. A value of 0 indicates that no
 *   data is available. No more than bd_limit BDs will be returned.
 *
 * @note Treat BDs returned by this function as read-only.
 *
 * @note This function should not be preempted by another FXmacBd function
 *       call that modifies the BD space. It is the caller's responsibility to
 *       provide a mutual exclusion mechanism.
 *
 *****************************************************************************/
u32 FXmacBdRingFromHwRx(FXmacBdRing *ring_ptr, u32 bd_limit,
                        FXmacBd **bd_set_ptr)
{
    FXmacBd *cur_bd_ptr;
    u32 bd_str = 0U;
    u32 bd_count;
    u32 bd_partial_count;
    u32 status;

    cur_bd_ptr = ring_ptr->hw_head;
    bd_count = 0U;
    bd_partial_count = 0U;

    /* If no BDs in work group, then there's nothing to search */
    if (ring_ptr->hw_cnt == 0x00000000U)
    {
        *bd_set_ptr = NULL;
        status = 0U;
    }
    else
    {

        /* Starting at hw_head, keep moving forward in the list until:
	 *  - A BD is encountered with its new/used bit set which means
	 *    hardware has completed processing of that BD.
	 *  - ring_ptr->hw_tail is reached and ring_ptr->hw_cnt is reached.
	 *  - The number of requested BDs has been processed
	 */
        while (bd_count < bd_limit)
        {

            /* Read the status */
            if (cur_bd_ptr != NULL)
            {
                bd_str = FXmacBdRead(cur_bd_ptr, FXMAC_BD_STAT_OFFSET);
            }
            if ((!(FXmacBdIsRxNew(cur_bd_ptr))) == TRUE)
            {
                break;
            }

            bd_count++;

            /* hardware has processed this BD so check the "last" bit. If
                 * it is clear, then there are more BDs for the current packet.
                 * Keep a count of these partial packet BDs.
		 */
            if ((bd_str & FXMAC_RXBUF_EOF_MASK) != 0x00000000U)
            {
                bd_partial_count = 0U;
            }
            else
            {
                bd_partial_count++;
            }

            /* Move on to next BD in work group */
            cur_bd_ptr = FXmacBdRingNext(ring_ptr, cur_bd_ptr);
        }

        /* Subtract off any partial packet BDs found */
        bd_count -= bd_partial_count;

        /* If bd_count is non-zero then BDs were found to return. Set return
	 * parameters, update pointers and counters, return success
	 */
        if (bd_count > 0x00000000U)
        {
            *bd_set_ptr = ring_ptr->hw_head;
            ring_ptr->hw_cnt -= bd_count;
            ring_ptr->post_cnt += bd_count;
            FXMAC_RING_SEEKAHEAD(ring_ptr, ring_ptr->hw_head, bd_count);
            status = (bd_count);
        }
        else
        {
            *bd_set_ptr = NULL;
            status = 0U;
        }
    }
    return status;
}

/*****************************************************************************/
/**
 * Frees a set of BDs that had been previously retrieved with
 * FXmacBdRingFromHw().
 *
 * @param ring_ptr is a pointer to the instance to be worked on.
 * @param num_bd is the number of BDs to free.
 * @param bd_set_ptr is the head of a list of BDs returned by
 * FXmacBdRingFromHw().
 *
 * @return
 *   - FT_SUCCESS if the set of BDs was freed.
 *   - FXMAC_ERR_SG_LIST if this function was called out of sequence with
 *     FXmacBdRingFromHw().
 *
 * @note This function should not be preempted by another FXmacBd function
 *       call that modifies the BD space. It is the caller's responsibility to
 *       provide a mutual exclusion mechanism.
 *
 *****************************************************************************/
LONG FXmacBdRingFree(FXmacBdRing *ring_ptr, u32 num_bd,
                     FXmacBd *bd_set_ptr)
{
    LONG status;
    /* if no bds to process, simply return. */
    if (0x00000000U == num_bd)
    {
        status = (LONG)(FT_SUCCESS);
    }
    else
    {
        /* Make sure we are in sync with FXmacBdRingFromHw() */
        if ((ring_ptr->post_cnt < num_bd) || (ring_ptr->post_head != bd_set_ptr))
        {
            status = (LONG)(FXMAC_ERR_SG_LIST);
        }
        else
        {
            /* Update pointers and counters */
            ring_ptr->free_cnt += num_bd;
            ring_ptr->post_cnt -= num_bd;
            FXMAC_RING_SEEKAHEAD(ring_ptr, ring_ptr->post_head, num_bd);
            status = (LONG)(FT_SUCCESS);
        }
    }
    return status;
}

/*****************************************************************************/
/**
 * Check the internal data structures of the BD ring for the provided channel.
 * The following checks are made:
 *
 *   - Is the BD ring linked correctly in physical address space.
 *   - Do the internal pointers point to BDs in the ring.
 *   - Do the internal counters add up.
 *
 * The channel should be stopped prior to calling this function.
 *
 * @param ring_ptr is a pointer to the instance to be worked on.
 * @param direction is either FXMAC_SEND or FXMAC_RECV that indicates
 *        which direction.
 *
 * @return
 *   - FT_SUCCESS if the set of BDs was freed.
 *   - XST_DMA_SG_NO_LIST if the list has not been created.
 *   - FXMAC_STATUS_IS_STARTED if the channel is not stopped.
 *   - FXMAC_ERR_SG_LIST if a problem is found with the internal data
 *     structures. If this value is returned, the channel should be reset to
 *     avoid data corruption or system instability.
 *
 * @note This function should not be preempted by another FXmacBd function
 *       call that modifies the BD space. It is the caller's responsibility to
 *       provide a mutual exclusion mechanism.
 *
 *****************************************************************************/
LONG FXmacBdRingCheck(FXmacBdRing *ring_ptr, u8 direction)
{
    uintptr addr_v, addr_p;
    u32 i;

    if ((direction != (u8)FXMAC_SEND) && (direction != (u8)FXMAC_RECV))
    {
        return (LONG)(FXMAC_ERR_INVALID_PARAM);
    }

    /* Is the list created */
    if (ring_ptr->all_cnt == 0x00000000U)
    {
        return (LONG)(FXMAC_ERR_SG_NO_LIST);
    }

    /* Can't check if channel is running */
    if (ring_ptr->run_state == (u32)FXMAC_DMA_SG_IS_STARTED)
    {
        return (LONG)(FXMAC_STATUS_IS_STARTED);
    }

    /* run_state doesn't make sense */
    if (ring_ptr->run_state != (u32)FXMAC_DMA_SG_IS_STOPED)
    {
        return (LONG)(FXMAC_ERR_SG_LIST);
    }

    /* Verify internal pointers point to correct memory space */
    addr_v = (uintptr)ring_ptr->free_head;
    if ((addr_v < ring_ptr->base_bd_addr) || (addr_v > ring_ptr->high_bd_addr))
    {
        return (LONG)(FXMAC_ERR_SG_LIST);
    }

    addr_v = (uintptr)ring_ptr->pre_head;
    if ((addr_v < ring_ptr->base_bd_addr) || (addr_v > ring_ptr->high_bd_addr))
    {
        return (LONG)(FXMAC_ERR_SG_LIST);
    }

    addr_v = (uintptr)ring_ptr->hw_head;
    if ((addr_v < ring_ptr->base_bd_addr) || (addr_v > ring_ptr->high_bd_addr))
    {
        return (LONG)(FXMAC_ERR_SG_LIST);
    }

    addr_v = (uintptr)ring_ptr->hw_tail;
    if ((addr_v < ring_ptr->base_bd_addr) || (addr_v > ring_ptr->high_bd_addr))
    {
        return (LONG)(FXMAC_ERR_SG_LIST);
    }

    addr_v = (uintptr)ring_ptr->post_head;
    if ((addr_v < ring_ptr->base_bd_addr) || (addr_v > ring_ptr->high_bd_addr))
    {
        return (LONG)(FXMAC_ERR_SG_LIST);
    }

    /* Verify internal counters add up */
    if ((ring_ptr->hw_cnt + ring_ptr->pre_cnt + ring_ptr->free_cnt +
         ring_ptr->post_cnt) != ring_ptr->all_cnt)
    {
        return (LONG)(FXMAC_ERR_SG_LIST);
    }

    /* Verify BDs are linked correctly */
    addr_v = ring_ptr->base_bd_addr;
    addr_p = ring_ptr->phys_base_addr + ring_ptr->separation;

    for (i = 1U; i < ring_ptr->all_cnt; i++)
    {
        /* Check BDA for this BD. It should point to next physical addr */
        if (FXmacBdRead(addr_v, FXMAC_BD_ADDR_OFFSET) != addr_p)
        {
            return (LONG)(FXMAC_ERR_SG_LIST);
        }

        /* Move on to next BD */
        addr_v += ring_ptr->separation;
        addr_p += ring_ptr->separation;
    }

    /* Last BD should have wrap bit set */
    if (FXMAC_SEND == direction)
    {
        if ((!FXmacBdIsTxWrap(addr_v)) == TRUE)
        {
            return (LONG)(FXMAC_ERR_SG_LIST);
        }
    }
    else
    { /* FXMAC_RECV */
        if ((!FXmacBdIsRxWrap(addr_v)) == TRUE)
        {
            return (LONG)(FXMAC_ERR_SG_LIST);
        }
    }

    /* No problems found */
    return (LONG)(FT_SUCCESS);
}

/*****************************************************************************/
/**
 * Set this bit to mark the last descriptor in the receive buffer descriptor
 * list.
 *
 * @param  BdPtr is the BD pointer to operate on
 *
 * @note
 * C-style signature:
 *    void FXMAC_BdSetRxWrap(FXMAC_Bd* BdPtr)
 *
 *****************************************************************************/
static void FXmacBdSetRxWrap(uintptr BdPtr)
{
    u32 DataValueRx;
    u32 *TempPtr;

    BdPtr += (u32)(FXMAC_BD_ADDR_OFFSET);
    TempPtr = (u32 *)BdPtr;
    if (TempPtr != NULL)
    {
        DataValueRx = *TempPtr;
        DataValueRx |= FXMAC_RXBUF_WRAP_MASK;
        *TempPtr = DataValueRx;
    }
}

/*****************************************************************************/
/**
 * Sets this bit to mark the last descriptor in the transmit buffer
 * descriptor list.
 *
 * @param  BdPtr is the BD pointer to operate on
 *
 * @note
 * C-style signature:
 *    void FXMAC_BdSetTxWrap(FXMAC_Bd* BdPtr)
 *
 *****************************************************************************/
static void FXMAC_BdSetTxWrap(uintptr BdPtr)
{
    u32 DataValueTx;
    u32 *TempPtr;

    BdPtr += (u32)(FXMAC_BD_STAT_OFFSET);
    TempPtr = (u32 *)BdPtr;
    if (TempPtr != NULL)
    {
        DataValueTx = *TempPtr;
        DataValueTx |= FXMAC_TXBUF_WRAP_MASK;
        *TempPtr = DataValueTx;
    }
}

/*****************************************************************************/
/**
 * Reset BD ring head and tail pointers.
 *
 * @param ring_ptr is the instance to be worked on.
 * @param virtaddrloc is the virtual base address of the user memory region.
 *
 * @note
 * Should be called after FXmacStop()
 *
 * @note
 * C-style signature:
 *    void FXmacBdring_ptrReset(FXmacBdRing * ring_ptr, void *virtaddrloc)
 *
 *****************************************************************************/
void FXmacBdring_ptrReset(FXmacBdRing *ring_ptr, void *virtaddrloc)
{
    ring_ptr->free_head = virtaddrloc;
    ring_ptr->pre_head = virtaddrloc;
    ring_ptr->hw_head = virtaddrloc;
    ring_ptr->hw_tail = virtaddrloc;
    ring_ptr->post_head = virtaddrloc;
}
